package edu.gmu.client;

import calendar.CalendarMgrInt;
import calendar.CalendarMgrStub;
import calendar.CalendarObjInt;
import calendar.CalendarStub;
import calendar.Event;
import calendar.Type;
import calendar.User;
import edu.gmu.messages.EndPoint;
import edu.gmu.messages.Resource;
import edu.gmu.messages.ListManagerResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * FrontEnd manages the three primary stubs
 * 
 * NamingServiceStub
 * CalendarMgrStubFE
 * CalendarStubFE
 * 
 * @author James Pope
 */
public class FrontEnd implements CalendarMgrInt
{
    private List<EndPoint> entryEndpoints = new ArrayList<EndPoint>();
    
    /**
     * Creates a new NamingServiceStub.
     * @param nameSeverEndpoint
     */
    public FrontEnd( )
    {
        entryEndpoints = new ArrayList<EndPoint>();
        entryEndpoints.add( EndPoint.deriveHostAddress(1) );
        entryEndpoints.add( EndPoint.deriveHostAddress(2) );
        entryEndpoints.add( EndPoint.deriveHostAddress(3) );
        entryEndpoints.add( EndPoint.deriveHostAddress(4) );
        entryEndpoints.add( EndPoint.deriveHostAddress(5) );
    }
    
//    /**
//     * Creates a new NamingServiceStub.
//     * @param nameSeverEndpoint
//     */
//    public FrontEnd( List<EndPoint> entryEndpoints )
//    {
//        this.entryEndpoints = entryEndpoints;
//    }
    
    public List<EndPoint> getEntryEndpoints()
    {
        return this.entryEndpoints;
    }
    
    /**
     * Utility to iterate the NameServers in the ring.
     * @return
     * @throws java.io.IOException
     */
    public RingIterator iterator() throws IOException
    {
        return new RingIterator(this.entryEndpoints);
    }
    
    
    //------------------------------------------------------------------------//
    public boolean connectCalendar(User user) throws IOException
    {
        CalendarMgrStub stub = this.getCalendarManagerStub(user, Type.PRIMARY);
        if( stub == null )
        {
            return false;
        }
        boolean result = stub.connectCalendar(user);
        return result;
    }

    public boolean disconnectCalendar(User user) throws IOException
    {
        CalendarMgrStub stub = this.getCalendarManagerStub(user, Type.PRIMARY );
        boolean result = stub.disconnectCalendar(user);
        return result;
    }

    public void isAlive() throws IOException
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public EndPoint getEndPoint() throws IOException
    {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    public boolean createCalendar( User user ) throws IOException
    {
        boolean result = false;
        CalendarMgrStub calendarMgrInt = this.getCalendarManagerStub(user, Type.PRIMARY);
        if( calendarMgrInt == null )
        {
            /*
             * CalendarMgr does not exist in the ring - go ahead and create
             * Create on nameServer with same id as name
             * We assume some order - so try best to keep servers in order
             */
            try
            {
                int prev = Integer.MAX_VALUE;
                int next = Integer.MIN_VALUE;
                int nameId = hashName( user.getName() );
                //System.out.println("Inserting " + nameId + " for "+ user.getName());
                RingIterator ringIter = this.iterator();
                while( ringIter.hasNext() )
                {
                    ListManagerResponse resourceResponse = ringIter.next();
                    next = resourceResponse.namingServiceId;
                    if( nameId == next ||
                        ( (nameId > prev) && (nameId < next) ) ||
                        ringIter.hasNext() == false ) // if last one add
                    {
                        //System.out.println("    creating user " + user+ " on "+resourceResponse.localhost);
                        NamingServiceStub namingServiceStub = new NamingServiceStub(resourceResponse.localhost);
                        calendarMgrInt = namingServiceStub.createManager(nameId);
                        break;
                    }
                    
                    prev = next;
                }
            }
            catch(IOException ioe)
            {
                ioe.printStackTrace();
                // ignore
            }
            
        }
        
        if( calendarMgrInt != null )
        {
            result = calendarMgrInt.createCalendar(user);
        }
        else
        {
            System.out.println("PROBLEM: Unable to find space to insert user " + user);
        }
        
        return result;
    }
    
    
    public ArrayList list() throws IOException
    {
        ArrayList users = new ArrayList();
        
        RingIterator ringIter = this.iterator();
        while( ringIter.hasNext() )
        {
            ListManagerResponse resourceResponse = ringIter.next();
            
            List<Resource> resources = resourceResponse.resources;
            for (int i = 0; i < resources.size(); i++)
            {
                Resource resource = resources.get(i);
                if( resource.type == Type.PRIMARY )
                {
                    CalendarMgrStub calendarMgrInt = new CalendarMgrStub( resource.endpoint );
                    
                    ArrayList oneList = calendarMgrInt.list();
                    users.addAll(oneList);
                }
            }
        }
        
        return users;
    }
    
    
    public CalendarMgrInt getCalendarManager( User user ) throws IOException
    {   
        return this.getCalendarManagerStub( user, Type.PRIMARY );
    }
    
    public CalendarObjInt connectCalendarObj( User user ) throws IOException
    {
        CalendarObjInt calStub = null;
        
        CalendarMgrStub stub = this.getCalendarManagerStub( user, Type.PRIMARY );
        
        boolean result = this.connectCalendar( user );
        if( result == true )
        {
            ArrayList users = stub.list();
            for (int i = 0; i < users.size(); i++)
            {
                User auser = (User)users.get(i);
                if( auser.equals(user) )
                {
                    // Found the correct user - use the one returned from list
                    calStub = new CalendarStub( auser.getEndPoint() );
                }
            }
        }
        
        return calStub;
    }
    
    public ArrayList retreiveEvents( User user, Date start, Date end ) throws IOException
    {
        ArrayList events = null;
        
        CalendarMgrStub stub = this.getCalendarManagerStub( user, Type.PRIMARY );
        
        if( stub != null )
        {
            ArrayList users = stub.list();
            for (int i = 0; i < users.size(); i++)
            {
                User auser = (User)users.get(i);
                if( auser.equals(user) )
                {
                    // Found the correct user - use the one returned from list
                    CalendarStub calStub = new CalendarStub( auser.getEndPoint() );
                    events = calStub.retrieveEvents(user, start, end);
                }
            }
        }
        
        return events;
    }
    
    /* this function must be complete without any disruptions */
    public CalendarStub getStub( User u, Type t ) throws IOException
    {
        CalendarStub calStub = null;
        CalendarMgrStub stub = this.getCalendarManagerStub( u, t );
        if( stub != null )
        {
          ArrayList userList = stub.list();

          for (int i = 0; i < userList.size(); i++)
          {
            User auser = (User)userList.get(i);
            if( auser.equals(u) )
            {
              // Found the correct user - use the one returned from list
              calStub = new CalendarStub( auser.getEndPoint() );
            }
          }
        }
        return calStub;
      }
    //------------------------------------------------------------------------//
    
    private CalendarMgrStub getCalendarManagerStub( User user, Type t ) throws IOException
    {   
        int nameId = hashName( user.getName() );
        
        CalendarMgrStub calendarMgrInt = null;
        RingIterator ringIter = this.iterator();
        while( ringIter.hasNext() )
        {
            ListManagerResponse resourceResponse = ringIter.next();
            
            List<Resource> resources = resourceResponse.resources;
            for (int i = 0; i < resources.size(); i++)
            {
                Resource resource = resources.get(i);
                if( resource.type == t && resource.id == nameId  )
                {
                    // Found matching calendar manager
                    //System.out.println("Connecting to manager-" + resource.endpoint);
                    calendarMgrInt = new CalendarMgrStub( resource.endpoint );
                    break;
                }
            }
            if( calendarMgrInt != null )
            {
                break;
            }
        }
        
        return calendarMgrInt;
    }
    
    /**
     * Gets hachCode for given name - consistent between equal names.
     * @param name
     * @return
     */
    public static int hashName( String name )
    {
        return (name.hashCode() % 5)+1;
    }
    
    /**
     * Test main
     * @param args
     * @throws java.io.IOException
     */
    public static void main( String[] args ) throws IOException
    {
        if( args.length != 1 )
        {
            String s = "java -cp ./dist/calendar.jar edu.gmu.client.FrontEnd <name>";
            System.out.println( s );
            return;
        }
        String name = args[0];
        
        
        FrontEnd frontEnd = new FrontEnd( );
        
        ArrayList users = frontEnd.list();
        System.out.println( "List All: " );
        for (int i = 0; i < users.size(); i++)
        {
            User user = (User)users.get(i);
            System.out.println( "    User: "+user );
        }
        
        
        User user = User.makeUser(name);
        CalendarMgrInt stub = frontEnd.getCalendarManager( user );
        if( stub != null )
        {
            boolean created = stub.createCalendar(user);
            System.out.println( "Created Calendar for "+user.getName()+"? "+created );
            
            boolean connected = stub.connectCalendar(user);
            System.out.println( "Connected Calendar for "+user.getName()+"? "+connected );
            boolean disconnected = stub.disconnectCalendar(user);
            if( disconnected == false)
            {
                System.out.println( "    unable to disconnect Calendar for "+user );
            }
            
            //System.out.println( "List CalendarManager for "+user.getName()+"? "+stub.list() );
        }
        else
        {
            System.out.println( "Unable to find CalendarMgr for "+user );
        }
    }
}